/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

#define NAME_LEN      10
#define TYPE_LEN      15
#define MODEL_LEN     10
#define SERVER_LEN    6
#define NCPUS_LEN     5
#define MAXMEM_LEN    10
#define MAXSWAP_LEN   8
#define RESOURCES_LEN 10

static json *nodeinfo_2json(struct QuickPoolHostInfo *, struct quickpoolInfo *);
static void nodeinfo_usage(const char *);
static unsigned int expand_list(char ***tolist, int *bitmaps, struct quickpoolInfo *info);
static int compare_nodeinfo(const void *x, const void *y);

/**
 * @brief       qp node info handler, to query node static information from jm.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      2             #3: argument has syntax error
 * @retval      3             #4: failed to query node static information
 * @retval      4             #5: not enough memory to print json string
 */
int
cmd_node_info(int argc, char **argv)
{
    char *strResReq = NULL;
    bool_t fOpt = FALSE;
    bool_t wOpt = FALSE;
    
    while (TRUE) {
        int option_index = 0;
        static struct option long_options[] = {
            {"requirement", required_argument, 0, 'R'},
            {"help", no_argument, 0, 'h'},
            {"format", no_argument, 0, 'f'},
            {"wide", no_argument, 0, 'w'},
            {NULL, 0, 0, 0}
        };

        int opt;
        opt = getopt_long(argc, argv, "R:h:fw", long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
        case 'R':
            if (PATH_MAX < strlen(optarg)) {
                fprintf(stderr, "The resource requirement string length is longer than %d.\n",
                        PATH_MAX);
                exit(1);
            }

            strResReq = optarg;

            break;
        case 'f':
            fOpt = TRUE;
            break;
        case 'w':
            wOpt = TRUE;
            break;
        case 'h':
            nodeinfo_usage(argv[0]);
            exit(0);
        case '?':
        default:
            nodeinfo_usage(argv[0]);
            exit(1);
        }
    }

    // if not specify node name, set all to TRUE
    int all=FALSE;
    unsigned int numNode;
    char **nodes=NULL, **nodePoint;
    numNode = get_argument_names(argc, argv, optind, &nodes, &all, "host");
    if ((all && numNode == 1) || !numNode) {
        nodePoint = NULL;
    } else {
        nodePoint = nodes;
    }

    struct quickpoolInfo *qpInfo=NULL;
    qpInfo = qp_info(NULL);
    if (NULL == qpInfo) { // failed to query quickpool base info.
        exit(2);
    }

    struct request nodeRequest;
    memset(&nodeRequest, 0, sizeof(struct request));

    nodeRequest.keyvalues = (struct keyvalue *)calloc(4, sizeof(struct keyvalue));
    if (NULL == nodeRequest.keyvalues) {
        fprintf(stderr, "calloc(%ld) failed due to %m.\n",
                4*sizeof(struct keyvalue));
        exit(4);
    }

    int n=0;
    nodeRequest.keyvalues[n].key = REQUEST_KEY_OPTIONS;
    nodeRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
    if (NULL == nodeRequest.keyvalues[n].value) {
        fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
        exit(4);
    }
    snprintf(nodeRequest.keyvalues[n].value, 12, "%d", 0);
    n ++;

    if (0 < numNode && NULL != nodePoint) {

        // to query specific nodes.
        nodeRequest.keyvalues[n].key = REQUEST_KEY_NUMBER;
        nodeRequest.keyvalues[n].value = (char *)calloc(12, sizeof(char));
        if (NULL == nodeRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(nodeRequest.keyvalues[n].value, 12, "%d", numNode);
        n ++;

        nodeRequest.keyvalues[n].key = REQUEST_KEY_NAMES;
        unsigned int i, size = 0;
        for (i = 0; i < numNode; i++) {
            size += strlen(nodePoint[i]) + 1;
        }
        nodeRequest.keyvalues[n].value = (char *)calloc(size+1, sizeof(char));
        if (NULL == nodeRequest.keyvalues[n].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
            exit(4);
        }

        for (i = 0; i < numNode; i++) {

            if (i == 0) {
                strncpy(nodeRequest.keyvalues[n].value, nodePoint[i], size);
            } else {
                strncat(nodeRequest.keyvalues[n].value, " ", size);
                strncat(nodeRequest.keyvalues[n].value, nodePoint[i], size);
            }
        }
        n ++;
    }

    if (NULL != strResReq) { // to select node satisfied with this requirement.
        nodeRequest.keyvalues[n].key = REQUEST_KEY_RESREQ;
        nodeRequest.keyvalues[n].value = copy_string(strResReq);
        n ++;
    }
    nodeRequest.number = n;

    quickpool_errno = 0;
    struct QuickPoolHostReply *hostReply;
    hostReply = qp_gethostinfo(&nodeRequest, NULL);
    if (quickpool_errno != 0 || NULL == hostReply) {

        if (NULL == hostReply) {
            fprintf(stderr, "%s\n", "Failed to query node info due to above reason.");
        } else {
            // JM replied error message.
            fprintf(stderr, "%s\n", hostReply->error_message);
            FREEUP(hostReply);
        }
        gf_xdr_free(gf_xdr_request, &nodeRequest);

        exit(3);
    }

    gf_xdr_free(gf_xdr_request, &nodeRequest);
    FREEUP(nodes);

    if (0 == hostReply->num_hosts) {
        fprintf(stderr, "No node info in the cluster.\n");
        exit(0);
    }

    qsort((struct QuickPoolHostInfo *)hostReply->hosts, hostReply->num_hosts, sizeof(struct QuickPoolHostInfo), compare_nodeinfo);
    
    /*option -f or --format args*/
    char buf[4096+1] = {0};
    char header[4096+1] = {0};
    char *strServer = NULL;
    char **resNames = NULL;
    unsigned int nRes;
    char ncpusStr[20+1] = {0};
    char maxmemStr[20+1] = {0};
    char maxswapStr[20+1] = {0};

    unsigned int i;
    if ((fOpt | wOpt) && hostReply->num_hosts > 0) {

        snprintf(buf, 4096, "%%-%ds  %%-%ds  %%-%ds  %%-%ds  %%%ds  %%%ds  %%%ds  %%%ds\n",
                 NAME_LEN, TYPE_LEN, MODEL_LEN, SERVER_LEN, NCPUS_LEN, MAXMEM_LEN, MAXSWAP_LEN, RESOURCES_LEN);
        snprintf(header, 4096, "%%-%ds  %%-%ds  %%-%ds  %%-%ds  %%%ds  %%%ds  %%%ds  %%%ds\n",
                 NAME_LEN, TYPE_LEN, MODEL_LEN, SERVER_LEN, NCPUS_LEN, MAXMEM_LEN, MAXSWAP_LEN, RESOURCES_LEN);
        fprintf(stdout,header, "NAME", "TYPE", "MODEL", "SERVER", "NCPUS", "MAXMEM", "MAXSWAP", "RESOURCES");
        
        for (i = 0; i < hostReply->num_hosts; i++){
            /*get SERVER value*/
            if (STATIC_SERVER == hostReply->hosts[i].server) {
                strServer = (char *)"Yes";
            } else if (DYNAMIC_SERVER == hostReply->hosts[i].server) {
                strServer = (char *)"Dyn";
            } else {
                strServer = (char *)"No";
            }
            
            /*get RESOURCES value*/
            nRes = expand_list(&resNames, hostReply->hosts[i].res_bitmap, qpInfo);
            if (4 == nRes){
                fprintf(stderr, "expand_list error.\n");
                gf_xdr_free(libxdr_host_reply, hostReply);
                FREEUP(hostReply);
        
                exit(4);
            }
            
            if (NULL == resNames){
                resNames = (char **)calloc(1, sizeof(char *));
                if (NULL == resNames) {
                    fprintf(stderr, "calloc(%ld) failed due to %m.\n", sizeof(char *));
                    exit(4);
                }
            }
            
            if (fOpt && !wOpt && strlen(hostReply->hosts[i].host) > NAME_LEN){
                hostReply->hosts[i].host[NAME_LEN-1] = '*';
                hostReply->hosts[i].host[NAME_LEN] = '\0';
            } 
            
            if (fOpt && !wOpt && strlen(hostReply->hosts[i].host_type) > TYPE_LEN){
                hostReply->hosts[i].host_type[TYPE_LEN-1] = '*';
                hostReply->hosts[i].host_type[TYPE_LEN] = '\0';
            }
            
            if (fOpt && !wOpt && strlen(hostReply->hosts[i].host_model) > MODEL_LEN){
                hostReply->hosts[i].host_model[MODEL_LEN-1] = '*';
                hostReply->hosts[i].host_model[MODEL_LEN] = '\0';
            }

            memset(ncpusStr, 0, sizeof(ncpusStr));
            memset(maxmemStr, 0, sizeof(maxmemStr));
            memset(maxswapStr, 0, sizeof(maxswapStr));

            if (hostReply->hosts[i].ncpus <= 0){
                strncpy(ncpusStr, "-", 20);
            }else{
                snprintf(ncpusStr, 12, "%d", hostReply->hosts[i].ncpus);
            }

            if (hostReply->hosts[i].maxmem <= 0){
                strncpy(maxmemStr, "-", 20);
            }else{
                snprintf(maxmemStr, 20, "%.1fG", hostReply->hosts[i].maxmem/1024);
            }

            if (hostReply->hosts[i].maxswap <= 0){
                strncpy(maxswapStr, "-", 20);
            }else{
                snprintf(maxswapStr, 20, "%.1fG", hostReply->hosts[i].maxswap/1024);
            }
            
            fprintf(stdout, buf, hostReply->hosts[i].host,
                    hostReply->hosts[i].host_type, hostReply->hosts[i].host_model, 
                    strServer, ncpusStr, maxmemStr, maxswapStr, *resNames);
        }
        
        gf_xdr_free(libxdr_host_reply, hostReply);
        FREEUP(hostReply);
        FREEUP(resNames);
        
        exit(0);
    }
    
    json *jsonNodeList = json_CreateObject();
    for (i=0; i<hostReply->num_hosts; i++) {

        json *jsonNode = nodeinfo_2json(&(hostReply->hosts[i]), qpInfo);
        json_AddItemToObject(jsonNodeList, hostReply->hosts[i].host, jsonNode);
    }

    char *strJson;
    strJson = json_Print(jsonNodeList);
    if (NULL != strJson) {
        fprintf(stdout, "%s\n", strJson);
        FREEUP(strJson);
    } else {

        perror("malloc");
        gf_xdr_free(libxdr_host_reply, hostReply);
        FREEUP(hostReply);
        json_Delete(jsonNodeList);

        exit(4);
    }
    json_Delete(jsonNodeList);

    gf_xdr_free(libxdr_host_reply, hostReply);
    FREEUP(hostReply);

    exit(0);
} // end function cmd_node_info

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       expand resource names from resource bitmap.
 *
 * @param[out]  tolist        #1: array to store resource names
 * @param[in]   bitmaps       #2: resource bitmap from JM
 * @param[in]   info          #3: load index and resource table
 *
 * @retval      > 0           #1: succeed
 * @retval      0             #2: host has no resource
 *
 * @note        caller shoud free the resource name array.
 */
static unsigned int
expand_list(char ***tolist, int *bitmaps, struct quickpoolInfo *info)
{
    if (NULL == info || 0 >= info->num_resources) {
        return 0;
    }

    char **strResNames;
    strResNames = (char **)calloc(info->num_resources, sizeof(char *));
    if (NULL == strResNames) {
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__,
                    "calloc", info->num_resources*sizeof(char*));
        exit(4);
    }

    unsigned int i, j;
    for (i=0, j=0; i<info->num_resources; i++) {
        bool_t hasit=FALSE;

        TEST_BIT(i, bitmaps, hasit);
        if (hasit) {
            strResNames[j++] = info->resource_table[i].name;
        }
    }

    if (0 < j) {
        *tolist = strResNames;
    } else {
        FREEUP(strResNames);
        *tolist = NULL;
    }

    return(j);
} // end function expand_list

/**
 * @brief       transform node info structure to json.
 *
 * @param[in]   node          #1: node to be tranformed
 * @param[in]   info          #2: load index and resource table
 *
 * @retval      not NULL      #1: transformed json object
 *
 * @note        if host is unavail, do not transfer static load
 */
static json *
nodeinfo_2json(struct QuickPoolHostInfo *node, struct quickpoolInfo *info)
{
    json *jsonNode = json_CreateObject();

    s2j_json_set_basic_element(jsonNode, node, string, "HOST_NAME", host);
    s2j_json_set_basic_element(jsonNode, node, string, "HOST_TYPE", host_type);
    s2j_json_set_basic_element(jsonNode, node, string, "HOST_MODEL", host_model);
    
    char *strServer;
    if (STATIC_SERVER == node->server) {
        strServer = (char *)"Yes";
    } else if (DYNAMIC_SERVER == node->server) {
        strServer = (char *)"Dyn";
    } else {
        strServer = (char *)"No";
    }

    json_AddStringToObject(jsonNode, "SERVER", strServer);

    char **resNames;
    unsigned int nRes;
    nRes = expand_list(&resNames, node->res_bitmap, info);
    if (0 < nRes) {

        json *jsonResArray;
        jsonResArray = json_CreateArray();

        unsigned int index=0;
        while (index < nRes) {
            json_AddItemToArray(jsonResArray, json_CreateString(resNames[index++]));
        }

        json_AddItemToObject(jsonNode, "RESOURCES", jsonResArray);
    }

    if (0 >= node->ncpus) {
        return jsonNode;
    }

    s2j_json_set_basic_element(jsonNode, node, int, "ncpus", ncpus);

    char *strUnit=(char *)"M";
    double maxMem=0.0;
    if (1024*1024 < node->maxmem) {
        maxMem = (double)node->maxmem/(double)(1024*1024);
        strUnit = (char *)"T";
    } else if (1024 < node->maxmem) {
        maxMem = (double)node->maxmem/(double)1024;
        strUnit = (char *)"G";
    } else if (0 < node->maxmem) {
        maxMem = node->maxmem;
        strUnit = (char *)"M";
    }

    if (0 < node->maxmem) {
        char strMaxMem[56+1];

        snprintf(strMaxMem, 56, "%.1f%s", maxMem, strUnit);
        json_AddStringToObject(jsonNode, "maxmem", strMaxMem);
    }

    double maxSwap=0.0;
    if (1024*1024 < node->maxswap) {
        maxSwap = (double)node->maxswap/(double)(1024*1024);
        strUnit = (char *)"T";
    } else if (1024 < node->maxswap) {
        maxSwap = (double)node->maxswap/(double)1024;
        strUnit = (char *)"G";
    } else if (0 < node->maxswap) {
        maxSwap = node->maxswap;
        strUnit = (char *)"M";
    }

    if (0 < node->maxswap) {
        char strMaxSwap[56+1];

        snprintf(strMaxSwap, 56, "%.1f%s", maxSwap, strUnit);
        json_AddStringToObject(jsonNode, "maxswap", strMaxSwap);
    }

    double maxTmp=0.0;
    if (1024*1024 < node->maxtmp) {
        maxTmp = (double)node->maxtmp/(double)(1024*1024);
        strUnit = (char *)"T";
    } else if (1024 < node->maxtmp) {
        maxTmp = (double)node->maxtmp/(double)1024;
        strUnit = (char *)"G";
    } else if (0 < node->maxtmp) {
        maxTmp = node->maxtmp;
        strUnit = (char *)"M";
    }

    if (0 < node->maxtmp) {
        char strMaxTmp[56+1];

        snprintf(strMaxTmp, 56, "%.1f%s", maxTmp, strUnit);
        json_AddStringToObject(jsonNode, "maxtmp", strMaxTmp);
    }

    double *li;
    li = node->load_threshold;

    json *jsonLoad;
    jsonLoad = json_CreateObject();

    unsigned int i;
    for (i=0; i < info->num_index; i++) {
        char strLoad[64+1];
        strLoad[0] = '\0';

        if (fabs(li[i]) >= (double)INFINIT_LOAD) {
            continue;
        }

        snprintf(strLoad, 64, "%.2f", li[i]);

        json_AddStringToObject(jsonLoad, info->resource_table[i].name, strLoad);
    }

    if (0 < json_GetArraySize(jsonLoad)) {
        json_AddItemToObject(jsonNode, "LOAD_THRESHOLD", jsonLoad);
    } else {
        json_Delete(jsonLoad);
    }

    return jsonNode;
} // end function nodeinfo_2json

static void
nodeinfo_usage(const char *cmd)
{
    fprintf(stdout, "qp node %s usage: [-h, --help] [-R, --requirement res_req] [-f, --format] [-w, --wide]\n", cmd);
    fprintf(stdout, "\t\t    [host_name ...]\n");

    return;
}

static int
compare_nodeinfo(const void *x, const void *y)
{
    struct QuickPoolHostInfo *node1;
    struct QuickPoolHostInfo *node2;
    node1 = (struct QuickPoolHostInfo *)x;
    node2 = (struct QuickPoolHostInfo *)y;

    return strcmp(node1->host,node2->host);
}
